package com.wsoft.bpm.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wsoft.bpm.constants.BpmConstant;
import com.wsoft.bpm.entity.ActWorkflowConfigEntity;
import com.wsoft.bpm.enums.ExecuteType;
import com.wsoft.bpm.enums.GatewayType;
import com.wsoft.bpm.enums.ListenerEvent;
import com.wsoft.bpm.enums.ListenerType;
import com.wsoft.bpm.mapper.ActWorkflowConfigMapper;
import com.wsoft.bpm.node.Process;
import com.wsoft.bpm.node.*;
import com.wsoft.bpm.query.node.*;
import com.wsoft.bpm.service.IActWorkflowConfigService;
import com.wsoft.constant.CommonConstant;
import com.wsoft.constant.RedisKeyConstant;
import com.wsoft.core.exception.Asserts;
import com.wsoft.core.service.RedisService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.ProcessDefinition;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author zhonglj
 * @since 2024年09月12日 10:00:59
 */
@Service
public class ActWorkflowConfigServiceImpl extends ServiceImpl<ActWorkflowConfigMapper, ActWorkflowConfigEntity> implements IActWorkflowConfigService {
    @Resource
    private RedisService redisService;
    @Resource
    private RepositoryService repositoryService;

    private String generateId(String name) {
        return name + "-" + IdUtil.randomUUID();
    }

    private void cacheJson(String id, String modelId, String jsonData) {
        String key = StrUtil.format(RedisKeyConstant.MODEL_DATA_CACHE_KEY_TEMPLATE, modelId, id);
        // json数据暂存缓存,缓存一天时间
        redisService.set(key, jsonData, 24 * 3600);
    }

    @Override
    public String convertXmlStartEvent(StartEventQuery req) {
        // 请求数据转换为xml
        StartEvent startEvent = new StartEvent();
        startEvent.setId(StrUtil.isBlank(req.getId()) ? generateId(BpmConstant.GENERATE_ID_PREFIX_START_EVENT) : req.getId());
        startEvent.setName(req.getName());
        startEvent.setInitiator(req.getInitiator());
        startEvent.setDocumentation(new Documentation().setTextNode(new TextNode().setText(req.getDocumentation())));

        if (StrUtil.isNotBlank(req.getMessageRef())) {
            startEvent.setMessageEventDefinition(
                    new MessageEventDefinition()
                            .setMessageRef(req.getMessageRef())
            );
        }

        if (StrUtil.isNotBlank(req.getStartCondition())) {
            List<ListenerQuery> listeners = CollUtil.isNotEmpty(req.getListeners()) ? req.getListeners() : CollUtil.newArrayList();
            // 判断是否已经创建开始事件
            Optional<ListenerQuery> find = listeners.stream().filter(e -> StrUtil.equals(e.getValue(), BpmConstant.START_PROCESS_ABLE_LISTENER)).findFirst();
            if (!find.isPresent()) {
                // 自动添加条件判断监听器
                ListenerQuery auto = new ListenerQuery();
                auto.setEvent(ListenerEvent.START.getValue());
                auto.setType(ListenerType.DELEGATE_EXPRESSION.getValue());
                auto.setValue(BpmConstant.START_PROCESS_ABLE_LISTENER);
                listeners.add(auto);
                req.setListeners(listeners);
            }
        }

        if (CollUtil.isNotEmpty(req.getListeners())) {
            startEvent.setExtensionElements(
                    new ExtensionElements().setExecutionListeners(
                            req.getListeners().stream()
                                    .map(e -> getExecutionListener(e.getType(), e.getValue()))
                                    .collect(Collectors.toList())
                    )
            );
        }

        return convert(startEvent, startEvent.getId(), req.getModelId(), JSONUtil.toJsonStr(req));
    }

    private ExecutionListener getExecutionListener(String type, String vale) {
        ExecutionListener listener = new ExecutionListener();
        // 根据不同的监听器类型设置属性
        if (StrUtil.equals(type, ListenerType.JAVA_CLASS.getValue())) {
            listener.setClz(vale);
        } else if (StrUtil.equals(type, ListenerType.EXPRESSION.getValue())) {
            listener.setExpression(vale);
        } else if (StrUtil.equals(type, ListenerType.DELEGATE_EXPRESSION.getValue())) {
            listener.setDelegateExpression(vale);
        } else {
            Asserts.fail("[bpm.def.error.listener.type]");
        }
        return listener;
    }

    @Override
    public String convertXmlSequenceFlow(SequenceFlowQuery req) {
        SequenceFlow sequenceFlow = new SequenceFlow();
        sequenceFlow.setId(StrUtil.isBlank(req.getId()) ? generateId("SequenceFlow") : req.getId());
        sequenceFlow.setName(req.getName());
        sequenceFlow.setSourceRef(req.getSourceRef());
        sequenceFlow.setTargetRef(req.getTargetRef());
        sequenceFlow.setSkipExpression(req.getSkipExpression());
        sequenceFlow.setDocumentation(new Documentation().setTextNode(new TextNode().setText(req.getDocumentation())));
        sequenceFlow.setConditionExpression(new FormalExpression()
                .setTextNode(new CdataTextNode().setText(req.getCondition())));

        if (CollUtil.isNotEmpty(req.getListeners())) {
            sequenceFlow.setExtensionElements(
                    new ExtensionElements().setExecutionListeners(
                            req.getListeners().stream()
                                    .map(e -> getExecutionListener(e.getType(), e.getValue()))
                                    .collect(Collectors.toList())
                    )
            );
        }

        return convert(sequenceFlow, sequenceFlow.getId(), req.getModelId(), JSONUtil.toJsonStr(req));
    }

    @Override
    public String convertXmlGateWay(GateWayQuery req) {
        String type = req.getType();
        if (StrUtil.equals(type, GatewayType.EVENT_BASED.getValue())) {
            EventBasedGateway gateway = new EventBasedGateway()
                    .setId(StrUtil.isBlank(req.getId()) ? generateId(BpmConstant.GENERATE_ID_PREFIX_EVENT_BASED_GATEWAY) : req.getId())
                    .setName(req.getName())
                    .setDocumentation(new Documentation().setTextNode(new TextNode().setText(req.getDocumentation())));
            if (CollUtil.isNotEmpty(req.getListeners())) {
                gateway.setExtensionElements(
                        new ExtensionElements().setExecutionListeners(
                                req.getListeners().stream()
                                        .map(e -> getExecutionListener(e.getType(), e.getValue()))
                                        .collect(Collectors.toList())
                        )
                );
            }

            return convert(gateway, gateway.getId(), req.getModelId(), JSONUtil.toJsonStr(req));

        } else if (StrUtil.equals(type, GatewayType.EXCLUSIVE.getValue())) {
            ExclusiveGateway gateway = new ExclusiveGateway()
                    .setId(StrUtil.isBlank(req.getId()) ? generateId(BpmConstant.GENERATE_ID_PREFIX_EXCLUSIVE_GATEWAY) : req.getId())
                    .setName(req.getName())
                    .setDocumentation(new Documentation().setTextNode(new TextNode().setText(req.getDocumentation())));
            if (CollUtil.isNotEmpty(req.getListeners())) {
                gateway.setExtensionElements(
                        new ExtensionElements().setExecutionListeners(
                                req.getListeners().stream()
                                        .map(e -> getExecutionListener(e.getType(), e.getValue()))
                                        .collect(Collectors.toList())
                        )
                );
            }

            return convert(gateway, gateway.getId(), req.getModelId(), JSONUtil.toJsonStr(req));

        } else if (StrUtil.equals(type, GatewayType.INCLUSIVE.getValue())) {

            InclusiveGateway gateway = new InclusiveGateway()
                    .setId(StrUtil.isBlank(req.getId()) ? generateId(BpmConstant.GENERATE_ID_PREFIX_INCLUSIVE_GATEWAY) : req.getId())
                    .setName(req.getName())
                    .setDocumentation(new Documentation().setTextNode(new TextNode().setText(req.getDocumentation())));
            if (CollUtil.isNotEmpty(req.getListeners())) {
                gateway.setExtensionElements(
                        new ExtensionElements().setExecutionListeners(
                                req.getListeners().stream()
                                        .map(e -> getExecutionListener(e.getType(), e.getValue()))
                                        .collect(Collectors.toList())
                        )
                );
            }
            return convert(gateway, gateway.getId(), req.getModelId(), JSONUtil.toJsonStr(req));

        } else if (StrUtil.equals(type, GatewayType.PARALLEL.getValue())) {

            ParallelGateway gateway = new ParallelGateway()
                    .setId(StrUtil.isBlank(req.getId()) ? generateId(BpmConstant.GENERATE_ID_PREFIX_PARALLEL_GATEWAY) : req.getId())
                    .setName(req.getName())
                    .setDocumentation(new Documentation().setTextNode(new TextNode().setText(req.getDocumentation())));
            if (CollUtil.isNotEmpty(req.getListeners())) {
                gateway.setExtensionElements(
                        new ExtensionElements().setExecutionListeners(
                                req.getListeners().stream()
                                        .map(e -> getExecutionListener(e.getType(), e.getValue()))
                                        .collect(Collectors.toList())
                        )
                );
            }

            return convert(gateway, gateway.getId(), req.getModelId(), JSONUtil.toJsonStr(req));
        } else {
            Asserts.fail("[bpm.def.error.gateway.nomatch]!");
        }

        return null;
    }

    @Override
    public String convertXmlUserTask(TaskQuery req) {
        // 验证检查
        customValidate(req);

        UserTask task = new UserTask();
        task.setId(StrUtil.isBlank(req.getId()) ? generateId(BpmConstant.GENERATE_ID_PREFIX_USERTASK) : req.getId());
        task.setName(req.getName());
        if (ExecuteType.ASSIGNEE.getValue().equals(req.getExecuteType())) {
            task.setAssignee(req.getExecuteExpression());

        } else if (ExecuteType.CANDIDATE_USER.getValue().equals(req.getExecuteType())) {
            task.setCandidateUsers(req.getExecuteExpression());

        } else if (ExecuteType.CANDIDATE_ORG.getValue().equals(req.getExecuteType())) {
            task.setCandidateGroups(ExecuteType.getCandidateGroups(req.getExecuteType(), req.getExecuteExpression()));

        } else if (ExecuteType.CANDIDATE_ROLE.getValue().equals(req.getExecuteType())) {
            task.setCandidateGroups(ExecuteType.getCandidateGroups(req.getExecuteType(), req.getExecuteExpression()));

        } else if (ExecuteType.CANDIDATE_LEVEL.getValue().equals(req.getExecuteType())) {
            task.setCandidateGroups(ExecuteType.getCandidateGroups(req.getExecuteType(), req.getExecuteExpression()));

        } else if (ExecuteType.CANDIDATE_POSITION.getValue().equals(req.getExecuteType())) {
            task.setCandidateGroups(ExecuteType.getCandidateGroups(req.getExecuteType(), req.getExecuteExpression()));

        } else if (ExecuteType.EXECUTE_EXPRESSION.getValue().equals(req.getExecuteType())) {
            //todo 表达式的定义和执行，例如:${parent.name like '张%'} 上级领导为张姓领导

        } else {
            Asserts.fail("[bpm.def.error.task.executeType]");
        }

        // 任务执行完成时间设置
        if (StrUtil.equals(req.getDueDateAction(), BpmConstant.TASK_DUE_DATE_ACTION_AUTORUN)) {
            // 时间格式转换
            String utcDatePattern = DateUtil.format(DateUtil.parse(req.getDueDate()), DatePattern.UTC_FORMAT);
            task.setDueDate(utcDatePattern);
        } else {
            // 默认不做任何反应

        }

        //todo 会签ui还没有定，现在页面上是类似于eclipse上的纯技术向的表现形式

        task.setFormKey(req.getFormKey());
        task.setPriority(req.getPriority());
        task.setSkipExpression(req.getSkipExpression());

        ExtensionElements extensionElements = new ExtensionElements()
                .setFormProperties(req.getFormProperties());
        if (CollUtil.isNotEmpty(req.getListeners())) {
            task.setExtensionElements(
                    new ExtensionElements().setExecutionListeners(
                            req.getListeners().stream()
                                    .map(e -> getExecutionListener(e.getType(), e.getValue()))
                                    .collect(Collectors.toList())
                    )
            );
        }
        task.setExtensionElements(extensionElements);

        return convert(task, task.getId(), req.getModelId(), JSONUtil.toJsonStr(req));
    }

    private void customValidate(TaskQuery req) {
        // 用户任务三选一控制
        checkThree(req);
    }

    private void checkThree(TaskQuery req) {
        int flag = 0;
        if (StrUtil.isNotBlank(req.getExecuteType()) || StrUtil.isNotBlank(req.getExecuteExpression())) {
            flag++;
        }
        if (StrUtil.isNotBlank(req.getDynamicApprovalType()) || StrUtil.isNotBlank(req.getDynamicApprovalValue())) {
            flag++;
        }
        //todo 后续优化多实例的判断方式
        if (StrUtil.isNotBlank(req.getIsSequential()) || StrUtil.isNotBlank(req.getLoopCardinality())) {
            flag++;
        }

        if (flag > 1) {
            // 只能三选一
            Asserts.fail("[bpm.process.error.checkThree.two]");
        }
    }

    @Override
    public String convertXmlTimeBoundaryEvent(TimeBoundaryEventQuery req) {
        TimeBoundaryEvent boundaryEvent = new TimeBoundaryEvent();
        boundaryEvent.setId(StrUtil.isBlank(req.getId()) ? generateId(BpmConstant.GENERATE_ID_PREFIX_TIME_BOUNDARY_EVENT) : req.getId());
        boundaryEvent.setName(req.getName());
        boundaryEvent.setAttachedToRef(req.getAttachedToRef());
        boundaryEvent.setCancelActivity(req.getCancelActivity());

        TimerEventDefinition definition = new TimerEventDefinition();
        if (StrUtil.isNotBlank(req.getTimeCycle())) {
            definition.setTimeCycle(new TimeCycle().setTextNode(new TextNode().setText(req.getTimeCycle())));
        }

        if (StrUtil.isNotBlank(req.getTimeDuration())) {
            definition.setTimeDuration(new TimeDuration().setTextNode(new TextNode().setText(req.getTimeDuration())));
        }

        if (StrUtil.isNotBlank(req.getTimeDate())) {
            definition.setTimeDate(new TimeDate().setTextNode(new TextNode().setText(req.getTimeDate())));
        }

        boundaryEvent.setTimerEventDefinition(definition);

        if (StrUtil.isNotBlank(req.getDocumentation())) {
            boundaryEvent.setDocumentation(new Documentation().setTextNode(new TextNode().setText(req.getDocumentation())));
        }

        if (CollUtil.isNotEmpty(req.getListeners())) {
            boundaryEvent.setExtensionElements(
                    new ExtensionElements().setExecutionListeners(
                            req.getListeners().stream()
                                    .map(e -> getExecutionListener(e.getType(), e.getValue()))
                                    .collect(Collectors.toList())
                    )
            );
        }

        return convert(boundaryEvent, boundaryEvent.getId(), req.getModelId(), JSONUtil.toJsonStr(req));
    }

    @Override
    public String convertXmlMessageBoundaryEvent(MessageBoundaryEventQuery req) {
        MessageBoundaryEvent boundaryEvent = new MessageBoundaryEvent();
        boundaryEvent.setId(StrUtil.isBlank(req.getId()) ? generateId(BpmConstant.GENERATE_ID_PREFIX_MESSAGE_BOUNDARY_EVENT) : req.getId());
        boundaryEvent.setName(req.getName());
        boundaryEvent.setAttachedToRef(req.getAttachedToRef());
        boundaryEvent.setCancelActivity(req.getCancelActivity());
        if (StrUtil.isNotBlank(req.getDocumentation())) {
            boundaryEvent.setDocumentation(new Documentation().setTextNode(new TextNode().setText(req.getDocumentation())));
        }

        if (CollUtil.isNotEmpty(req.getListeners())) {
            boundaryEvent.setExtensionElements(
                    new ExtensionElements().setExecutionListeners(
                            req.getListeners().stream()
                                    .map(e -> getExecutionListener(e.getType(), e.getValue()))
                                    .collect(Collectors.toList())
                    )
            );
        }

        boundaryEvent.setMessageEventDefinition(new MessageEventDefinition().setMessageRef(req.getMessageRef()));
        return convert(boundaryEvent, boundaryEvent.getId(), req.getModelId(), JSONUtil.toJsonStr(req));
    }

    @Override
    public String convertXmlEndEvent(EndEventQuery req) {
        EndEvent endEvent = new EndEvent();
        endEvent.setId(StrUtil.isBlank(req.getId()) ? generateId(BpmConstant.GENERATE_ID_PREFIX_END_EVENT) : req.getId());
        endEvent.setName(req.getName());
        endEvent.setDocumentation(new Documentation().setTextNode(new TextNode().setText(req.getDocumentation())));

        // 增加一个结束事件监听器
        List<ListenerQuery> listeners = CollUtil.isNotEmpty(req.getListeners()) ? req.getListeners() : CollUtil.newArrayList();
        ListenerQuery endCallBack = new ListenerQuery();
        endCallBack.setEvent(ListenerEvent.END.getValue());
        endCallBack.setType(ListenerType.DELEGATE_EXPRESSION.getValue());
        endCallBack.setValue(BpmConstant.END_EVENT_CALL_BACK_DELEGATE_EXPRESSION);
        listeners.add(endCallBack);
        req.setListeners(listeners);

        if (CollUtil.isNotEmpty(req.getListeners())) {
            endEvent.setExtensionElements(
                    new ExtensionElements().setExecutionListeners(
                            req.getListeners().stream()
                                    .map(e -> getExecutionListener(e.getType(), e.getValue()))
                                    .collect(Collectors.toList())
                    )
            );
        }

        if (StrUtil.isNotBlank(req.getErrorRef())) {
            endEvent.setErrorEventDefinition(new ErrorEventDefinition().setErrorRef(req.getErrorRef()));
        }

        return convert(endEvent, endEvent.getId(), req.getModelId(), JSONUtil.toJsonStr(req));
    }

    @Override
    public void updateAfterDeploy(String modelId, String deploymentId) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).singleResult();
        if (processDefinition == null) {
            Asserts.fail("[bpm.def.error.empty.processDef]");
        }

        // 更新json配置
        List<ActWorkflowConfigEntity> lastConfigs = list(Wrappers.lambdaQuery(ActWorkflowConfigEntity.class)
                .eq(ActWorkflowConfigEntity::getModelId, modelId)
                .eq(ActWorkflowConfigEntity::getDeleted, CommonConstant.YESNO_NO)
                .isNull(ActWorkflowConfigEntity::getProcDefId)
        );
        if (CollUtil.isNotEmpty(lastConfigs)) {
            lastConfigs.stream().forEach(e -> e.setProcDefId(processDefinition.getId()));
            // 关联proc_def_id
            updateBatchById(lastConfigs);

            // 新增最新数据到待修改
            lastConfigs.stream().forEach(e -> {
                e.setProcDefId(null);
                e.setId(null);
            });
            saveBatch(lastConfigs);
        }

    }

    @Override
    public String convertXmlProcess(ProcessQuery req) {
        Process process = new Process();
        process.setId(StrUtil.isBlank(req.getId()) ? generateId(BpmConstant.GENERATE_ID_PREFIX_PROCESS) : req.getId());
        process.setName(req.getName());
        if (StrUtil.isNotBlank(req.getDocumentation())) {
            process.setDocumentation(new Documentation().setTextNode(new TextNode().setText(req.getDocumentation())));
        }
        if (CollUtil.isNotEmpty(req.getListeners())) {
            process.setExtensionElements(
                    new ExtensionElements().setExecutionListeners(
                            req.getListeners().stream()
                                    .map(e -> getExecutionListener(e.getType(), e.getValue()))
                                    .collect(Collectors.toList())
                    )
            );
        }

        process.setCandidateStarterUsers(req.getCandidateStarterUsers());
        process.setCandidateStarterGroups(req.getCandidateStarterGroups());
        return convert(process, process.getId(), req.getModelId(), JSONUtil.toJsonStr(req));
    }

    @Override
    public String convertXmlTimeIntermediateEvent(TimeIntermediateEventQuery req) {
        TimeIntermediateEvent intermediateEvent = new TimeIntermediateEvent();
        intermediateEvent.setId(StrUtil.isBlank(req.getId()) ? generateId(BpmConstant.GENERATE_ID_PREFIX_TIME_INTERMEDIATE_EVENT) : req.getId());
        intermediateEvent.setName(req.getName());

        TimerEventDefinition definition = new TimerEventDefinition();
        if (StrUtil.isNotBlank(req.getTimeCycle())) {
            definition.setTimeCycle(new TimeCycle().setTextNode(new TextNode().setText(req.getTimeCycle())));
        }

        if (StrUtil.isNotBlank(req.getTimeDuration())) {
            definition.setTimeDuration(new TimeDuration().setTextNode(new TextNode().setText(req.getTimeDuration())));
        }

        if (StrUtil.isNotBlank(req.getTimeDate())) {
            definition.setTimeDate(new TimeDate().setTextNode(new TextNode().setText(req.getTimeDate())));
        }

        intermediateEvent.setTimerEventDefinition(definition);

        if (StrUtil.isNotBlank(req.getDocumentation())) {
            intermediateEvent.setDocumentation(new Documentation().setTextNode(new TextNode().setText(req.getDocumentation())));
        }

        if (CollUtil.isNotEmpty(req.getListeners())) {
            intermediateEvent.setExtensionElements(
                    new ExtensionElements().setExecutionListeners(
                            req.getListeners().stream()
                                    .map(e -> getExecutionListener(e.getType(), e.getValue()))
                                    .collect(Collectors.toList())
                    )
            );
        }

        return convert(intermediateEvent, intermediateEvent.getId(), req.getModelId(), JSONUtil.toJsonStr(req));
    }

    @Override
    public String convertXmlMessageIntermediateEvent(MessageIntermediateEventQuery req) {
        MessageIntermediateEvent intermediateEvent = new MessageIntermediateEvent();
        intermediateEvent.setId(StrUtil.isBlank(req.getId()) ? generateId(BpmConstant.GENERATE_ID_PREFIX_MESSAGE_INTERMEDIATE_EVENT) : req.getId());
        intermediateEvent.setName(req.getName());
        if (StrUtil.isNotBlank(req.getDocumentation())) {
            intermediateEvent.setDocumentation(new Documentation().setTextNode(new TextNode().setText(req.getDocumentation())));
        }

        if (CollUtil.isNotEmpty(req.getListeners())) {
            intermediateEvent.setExtensionElements(
                    new ExtensionElements().setExecutionListeners(
                            req.getListeners().stream()
                                    .map(e -> getExecutionListener(e.getType(), e.getValue()))
                                    .collect(Collectors.toList())
                    )
            );
        }

        if (StrUtil.isNotBlank(req.getMessageRef())) {
            intermediateEvent.setMessageEventDefinition(new MessageEventDefinition().setMessageRef(req.getMessageRef()));
        }

        return convert(intermediateEvent, intermediateEvent.getId(), req.getModelId(), JSONUtil.toJsonStr(req));
    }

    @Override
    public void clearModelCache(ProcessQuery req) {
        // 根据modelId清除缓存
        String modelId = req.getModelId();
        String key = StrUtil.format(RedisKeyConstant.MODEL_DATA_CACHE_KEY_FIND_BY_ID, modelId);
        redisService.deleteByKeyPattern(key);
    }

    @Nullable
    private String convert(ActivityXml activityXml, String id, String modelId, String jsonData) {
        try {
            // xml解析成string返回
            String convertXml = activityXml.convert();
            // 缓存数据
            cacheJson(id, modelId, jsonData);
            return convertXml;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            Asserts.fail("[bpm.def.error.xml.convert]");
        }

        return null;
    }
}
